/*
 * Copyright (C) 2012 David Bordoley
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package restlib.net;

import java.net.IDN;
import java.net.InetAddress;

import javax.annotation.concurrent.NotThreadSafe;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.net.InternetDomainName;

/**
 * A builder for generating syntatically valid URIs. UriBuilder instances can be reused; 
 * it is safe to call build() multiple times to build multiple URIs.
 */
@NotThreadSafe
public final class UriBuilder extends IRIBuilder {
    UriBuilder(){}

    /**
     * Returns a new {@code Uri} instance.
     * @throws IllegalStateException If the Uri instance that would be generated by this 
     * builder is not a syntactically valid URI.
     */
    @Override
    public Uri build() {
        final Uri retval = new Uri(this);
        Preconditions.checkState(retval.isValid());
        return retval;
    }

    /**
     * Sets the authority component.
     * @param authority a non-null syntactically valid URI authority component.
     * @return this {@code UriBuilder} instance.
     * @throws NullPointerException if authority is null.
     * @throws IllegalArgumentException if authority is not a syntactically valid
     * URI authority component.
     */
    @Override
    public UriBuilder setAuthority(final String authority) {
        super.setAuthority(authority);
        return this;
    }

    /**
     * Sets the fragment component of the {@code UriBuilder}.
     * @param fragment a valid URI fragment string.
     * @return this {@code UriBuilder} instance.
     * @throws NullPointerException if {@code fragment} is null.
     * @throws IllegalArgumentException if {@code fragment} contains
     * characters or code points not allowed in the URI fragment component.
     */
    @Override
    public UriBuilder setFragment(final String fragment) {
        Preconditions.checkNotNull(fragment);
        Preconditions.checkArgument(IRIPredicates.IS_FRAGMENT.apply(fragment));
        this.fragment = fragment;
        return this;
    }

    /**
     * Sets the host component of the {@code UriBuilder} from an
     * {@code InetAddress}.
     * @param host any non-null IPv4 or IPv6 address.
     * @return this {@code UriBuilder} instance.
     * @throws NullPointerException if host is null. 
     */
    @Override
    public UriBuilder setHost(final InetAddress host) {
        super.setHost(host);
        return this;
    }
    
    /**
     * Sets the host component of the {@code UriBuilder} from an
     * {@code InternetDomainName}.
     * @param host any non-null domain name including international domain names.
     * @return this {@code UriBuilder} instance.
     * @throws NullPointerException if host is null. 
     */
    @Override
    public UriBuilder setHost(final InternetDomainName host) {
        super.setHost(IDN.toASCII(host.name()));
        return this;
    }
    
    /**
     * Sets the host of the {@code UriBuilder}. 
     * @param host any valid URI host component.
     * @return this {@code UriBuilder} instance.
     * @throws NullPointerException if host is null. 
     * @throws IllegalArgumentException if host is not a valid URI host.
     */
    @Override
    public UriBuilder setHost(final String host) {
        Preconditions.checkArgument(IRIPredicates.IS_URI_CHARS.apply(host));
        super.setHost(host);
        return this;
    }

    /**
     * Sets the specific path component of the {@code UriBuilder}.
     * @param path a {@code Path} instance that is a valid URI path.
     * @return this {@code UriBuilder} instance.
     * @throws NullPointerException if {@code path} is null.
     * @throws IllegalArgumentException if {@code path} is not a valid URI path.
     */
    @Override
    public UriBuilder setPath(final Path path) {
        Preconditions.checkNotNull(path);
        Preconditions.checkArgument(path.isUriPath());
        this.path = path;
        return this;
    }

    /**
     * Sets the path component of the {@code UriBuilder}.
     * @param path a {@code String} instance that is a valid URI path.
     * @return this {@code UriBuilder} instance.
     * @throws NullPointerException if {@code path} is null.
     * @throws IllegalArgumentException if {@code path} is not a valid URI path.
     */
    @Override
    public UriBuilder setPath(final String path) {
        return setPath(Path.parse(path));
    }

    /**
     * Set the port component of the {@code UriBuilder}.
     * @param port any valid TCP port number.
     * @return this {@code UriBuilder} instance.
     * @throws IllegalArgumentException if {@code port} is not a valid TCP port number.
     */
    @Override
    public UriBuilder setPort(final int port) {
        super.setPort(port);
        return this;
    }
    
    @Override
    UriBuilder setPort(final Optional<Integer> port) {
        super.setPort(port);
        return this;
    }

    /**
     * Sets the query component of the {@code UriBuilder}.
     * @param query a valid URI query string
     * @return this {@code UriBuilder} instance.
     * @throws NullPointerException if {@code query} is null.
     * @throws IllegalArgumentException if {@code query} contains
     * characters or code point not allowed in the URI query component.
     */
    @Override
    public UriBuilder setQuery(final String query) {
        Preconditions.checkNotNull(query);
        Preconditions.checkArgument(IRIPredicates.IS_QUERY.apply(query));
        this.query = query;
        return this;
    }

    /**
     * Sets the scheme component of the {@code UriBuilder}.
     * @param scheme a valid URI scheme.
     * @return this {@code UriBuilder} instance.
     * @throws NullPointerException if {@code scheme} is null.
     * @throws IllegalArgumentException if {@code scheme} is not a 
     * syntatically valid URI scheme.
     */
    @Override
    public UriBuilder setScheme(final String scheme) {
        super.setScheme(scheme);
        return this;
    }

    /**
     * Sets the userinfo component of the {@code UriBuilder}.
     * @param userinfo a valid URI userinfo component.
     * @return this {@code UriBuilder} instance.
     * @throws NullPointerException if {@code userinfo} is null.
     * @throws IllegalArgumentException if {@code userinfo} is not a 
     * syntatically valid URI userinfo component.
     */
    @Override
    public UriBuilder setUserinfo(final String userinfo) {
        Preconditions.checkNotNull(userinfo);
        Preconditions.checkArgument(IRIPredicates.IS_USER_INFO.apply(userinfo));
        this.userinfo = userinfo;
        return this;
    }
}
